﻿using System;
using System.Collections.Generic;

namespace Minecraft.WorldMap
{
	public sealed class RegionFile : IEnumerable<KeyValuePair<Tuple<int, int>, NamedBinaryTag.TagFile>>, IEnumerable<KeyValuePair<Tuple<int, int>, DateTime>>, IEnumerable<KeyValuePair<Tuple<int, int>, Tuple<DateTime, NamedBinaryTag.TagFile>>>, System.Collections.IEnumerable
	{
		private NamedBinaryTag.TagFile[,] chunks;
		private DateTime?[,] updated;

		private sealed class InternalCollection<T> : IList<IList<T>>, System.Collections.Specialized.INotifyCollectionChanged
		{
			private sealed class InternalCollection2: IList<T>, System.Collections.Specialized.INotifyCollectionChanged
			{
				internal delegate T GetItemDelegate(int z);
				internal delegate void SetItemDelegate(int z, T value);

				private GetItemDelegate getItem;
				private SetItemDelegate setItem;

				internal InternalCollection2(GetItemDelegate getItem, SetItemDelegate setItem)
				{
					this.getItem = getItem;
					this.setItem = setItem;
				}

				#region IList<T> Members

				public int IndexOf(T item)
				{
					for (int i = 0; i < Count; i++)
						if (item.Equals(getItem(i)))
							return i;
					return -1;
				}

				public void Insert(int index, T item)
				{
					throw new NotSupportedException();
				}

				public void RemoveAt(int index)
				{
					throw new NotSupportedException();
				}

				public T this[int index]
				{
					get
					{
						return getItem(index);
					}
					set
					{
						setItem(index, value);
					}
				}

				#endregion

				#region ICollection<T> Members

				public void Add(T item)
				{
					throw new NotSupportedException();
				}

				public void Clear()
				{
					throw new NotSupportedException();
				}

				public bool Contains(T item)
				{
					return IndexOf(item) >= 0;
				}

				public void CopyTo(T[] array, int arrayIndex)
				{
					if (array == null)
						throw new ArgumentNullException("array");
					if (arrayIndex < 0)
						throw new ArgumentOutOfRangeException("arrayIndex");
					if (array.Length + arrayIndex < Count)
						throw new ArgumentException("Not enought space in destination array.");
					for (int i = 0; i < Count; i++)
						array[i + arrayIndex] = getItem(i);
				}

				public int Count
				{
					get { return RegionHelper.chunkDepth; }
				}

				public bool IsReadOnly
				{
					get { return true; }
				}

				public bool Remove(T item)
				{
					throw new NotSupportedException();
				}

				#endregion

				#region IEnumerable<T> Members

				public IEnumerator<T> GetEnumerator()
				{
					for (int i = 0; i < RegionHelper.chunkDepth; i++)
						yield return this.getItem(i);
				}

				#endregion

				#region IEnumerable Members

				System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
				{
					return this.GetEnumerator();
				}

				#endregion

				#region INotifyCollectionChanged Members

				public event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged;

				#endregion
			}

			internal delegate T GetItemDelegate(int x, int z);
			internal delegate void SetItemDelegate(int x, int z, T value);

			private InternalCollection2[] internalCollections;

			internal InternalCollection(GetItemDelegate getItem, SetItemDelegate setItem)
			{
				internalCollections = new InternalCollection2[RegionHelper.chunkWidth];
				for (int i = 0; i < internalCollections.Length; i++)
				{
					internalCollections[i] = new InternalCollection2(MakeGetItem(getItem, i), MakeSetItem(setItem, i));
				}
			}

			private InternalCollection2.GetItemDelegate MakeGetItem(GetItemDelegate getItem, int index)
			{
				return delegate(int z) { return getItem(index, z); };
			}

			private InternalCollection2.SetItemDelegate MakeSetItem(SetItemDelegate setItem, int index)
			{
				return (setItem != null) ? delegate(int z, T value) { setItem(index, z, value); } : (InternalCollection2.SetItemDelegate)null;
			}

			#region IList<IList<T>> Members

			public int IndexOf(IList<T> item)
			{
				for (int i = 0; i < internalCollections.Length; i++)
					if (item == internalCollections[i])
						return i;
				return -1;
			}

			public void Insert(int index, IList<T> item)
			{
				throw new NotSupportedException();
			}

			public void RemoveAt(int index)
			{
				throw new NotSupportedException();
			}

			public IList<T> this[int x]
			{
				get
				{
					return this.internalCollections[x];
				}
				set
				{
					throw new NotSupportedException();
				}
			}

			#endregion

			#region ICollection<IList<T>> Members

			public void Add(IList<T> item)
			{
				throw new NotSupportedException();
			}

			public void Clear()
			{
				throw new NotSupportedException();
			}

			public bool Contains(IList<T> item)
			{
				return IndexOf(item) >= 0;
			}

			public void CopyTo(IList<T>[] array, int arrayIndex)
			{
				if (array == null)
					throw new ArgumentNullException("array");
				if (arrayIndex < 0)
					throw new ArgumentOutOfRangeException("arrayIndex");
				if (array.Length + arrayIndex < internalCollections.Length)
					throw new ArgumentException("Not enought space in destination array.");
				for (int i = 0; i < internalCollections.Length; i++)
					array[i + arrayIndex] = internalCollections[i];
			}

			public int Count
			{
				get { return internalCollections.Length; }
			}

			public bool IsReadOnly
			{
				get { return true; }
			}

			public bool Remove(IList<T> item)
			{
				throw new NotSupportedException();
			}

			#endregion

			#region IEnumerable<IList<T>> Members

			public IEnumerator<IList<T>> GetEnumerator()
			{
				for (int i = 0; i < internalCollections.Length; i++)
					yield return (IList<T>)internalCollections[i];
			}

			#endregion

			#region IEnumerable Members

			System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
			{
				return this.GetEnumerator();
			}

			#endregion

			#region INotifyCollectionChanged Members

			public event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged;

			#endregion

			internal void NotifyCollectionReset()
			{
				if (CollectionChanged != null)
					CollectionChanged(this, new System.Collections.Specialized.NotifyCollectionChangedEventArgs(System.Collections.Specialized.NotifyCollectionChangedAction.Reset));
			}
		}

		private InternalCollection<NamedBinaryTag.TagFile> chunksCollection;
		private InternalCollection<DateTime?> updatedCollection;
		private InternalCollection<bool> activeCollection;
		private InternalCollection<Tuple<DateTime?, NamedBinaryTag.TagFile>> combinedCollection;

		public IList<IList<NamedBinaryTag.TagFile>> Chunks
		{
			get { return chunksCollection; }
		}

		public IList<IList<DateTime?>> UpdateTimes
		{
			get { return updatedCollection; }
		}

		public IList<IList<bool>> ActiveChunks
		{
			get { return activeCollection; }
		}

		public IList<IList<Tuple<DateTime?, NamedBinaryTag.TagFile>>> Combined
		{
			get { return combinedCollection; }
		}

		public RegionFile()
		{
			chunks = new NamedBinaryTag.TagFile[RegionHelper.chunkWidth, RegionHelper.chunkDepth];
			for (int x = 0; x < RegionHelper.chunkWidth; x++)
				for (int z = 0; z < RegionHelper.chunkDepth; z++)
					chunks[x, z] = new NamedBinaryTag.TagFile();
			updated = new DateTime?[RegionHelper.chunkWidth, RegionHelper.chunkDepth];
#if !SILVERLIGHT
			// if (System.ComponentModel.LicenseManager.UsageMode == System.ComponentModel.LicenseUsageMode.Designtime)
			{
				for (int x = 0; x < RegionHelper.chunkWidth; x++)
					for (int z = 0; z < RegionHelper.chunkDepth; z++)
					{
						if ((x + z) % 2 != 0)
							continue;
						updated[x, z] = DateTime.Now;
						NamedBinaryTag.TagCompound tag = new NamedBinaryTag.TagCompound() { Name = "Level" };
						tag.Items.Add(new NamedBinaryTag.TagInt() { Name = "xPos", Value = x });
						tag.Items.Add(new NamedBinaryTag.TagInt() { Name = "zPos", Value = z });
						chunks[x, z].Init();
						chunks[x, z].RootTag.Items.Add(tag);
					}
			}
#endif
			chunksCollection = new InternalCollection<NamedBinaryTag.TagFile>(delegate(int x, int z) { return chunks[x, z]; }, null);
			updatedCollection = new InternalCollection<DateTime?>(delegate(int x, int z) { return updated[x, z]; }, delegate(int x, int z, DateTime? value) { updated[x, z] = value; });
			activeCollection = new InternalCollection<bool>(delegate(int x, int z) { return (chunks[x, z].RootTag != null) && (updated[x, z].HasValue); }, null);
			combinedCollection = new InternalCollection<Tuple<DateTime?,NamedBinaryTag.TagFile>>(delegate(int x, int z) { return new Tuple<DateTime?, NamedBinaryTag.TagFile>(updated[x, z], chunks[x, z]);}, null);
		}

		public void Load(System.IO.Stream input)
		{
			using (SectorizedStream sectInput = new SectorizedStream(input, RegionHelper.sectorSize))
			{
				SortedList<int, Tuple<int, int, byte>> chunksLocation = RegionHelper.ReadChunksLocations(sectInput);
				DateTime?[,] updated = RegionHelper.ReadUpdateTimes(sectInput);
				NamedBinaryTag.TagFile[,] chunks = ReadChunks(sectInput, chunksLocation);
				this.updated = updated;
				for (int x = 0; x < RegionHelper.chunkWidth; x++)
					for (int z = 0; z < RegionHelper.chunkDepth; z++)
						this.chunks[x, z].Swap(chunks[x, z]);
				updatedCollection.NotifyCollectionReset();
				activeCollection.NotifyCollectionReset();
				combinedCollection.NotifyCollectionReset();
			}
		}

		private NamedBinaryTag.TagFile[,] ReadChunks(SectorizedStream input, SortedList<int, Tuple<int, int, byte>> chunksLocation)
		{
			NamedBinaryTag.TagFile[,] chunks = new NamedBinaryTag.TagFile[RegionHelper.chunkWidth, RegionHelper.chunkDepth];
			for (int x = 0; x < RegionHelper.chunkWidth; x++)
				for (int z = 0; z < RegionHelper.chunkDepth; z++)
					chunks[x, z] = new NamedBinaryTag.TagFile();
			foreach (var chunkLocation in chunksLocation)
			{
				ReadChunk(input, chunkLocation.Key, chunks[chunkLocation.Value.Item1, chunkLocation.Value.Item2]);
			}
			return chunks;
		}

		private void ReadChunk(SectorizedStream input, int startSector, NamedBinaryTag.TagFile chunkFile)
		{
#if DEBUG
			if (NamedBinaryTag.DebugFlags.GetDebug(6))
				Console.WriteLine("Reading Chunk: {{ Start = {0} }}", startSector);
#endif
			int length;
			NamedBinaryTag.CompressionType compType;
			input.SkipToSector(startSector);
			using (System.IO.Stream dumper = new NamedBinaryTag.DumpStream(input))
			{
				using (System.IO.BinaryReader reader = new System.IO.BinaryReader(dumper))
				{
					length = NamedBinaryTag.ByteReverser.Reverse(reader.ReadInt32());
					compType = (NamedBinaryTag.CompressionType)reader.ReadByte();
				}
			}
			using (System.IO.Stream segment = new StreamFragment(input, length - 1))
			{
				chunkFile.Load(segment, compType);
			}
		}

		#region Timestamp convertion

		#endregion

		#region INotifyPropertyChanged Members

		public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

		#endregion

		protected void RaisePropertyChange(string propertyName)
		{
			if (PropertyChanged != null)
				PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
		}

		#region IEnumerable<KeyValuePair<Tuple<int,int>,DataFile>> Members

		public IEnumerator<KeyValuePair<Tuple<int, int>, NamedBinaryTag.TagFile>> GetEnumerator()
		{
			for (int z = 0; z < RegionHelper.chunkDepth; z++)
				for (int x = 0; x < RegionHelper.chunkWidth; x++)
					if (chunks[x, z].RootTag != null)
						yield return new KeyValuePair<Tuple<int, int>, NamedBinaryTag.TagFile>(new Tuple<int, int>(x, z), chunks[x, z]);
		}

		#endregion

		#region IEnumerable Members

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return ((IEnumerable<KeyValuePair<Tuple<int, int>, Tuple<DateTime, NamedBinaryTag.TagFile>>>)this).GetEnumerator();
		}

		#endregion

		#region IEnumerable<KeyValuePair<Tuple<int,int>,DateTime>> Members

		IEnumerator<KeyValuePair<Tuple<int, int>, DateTime>> IEnumerable<KeyValuePair<Tuple<int, int>, DateTime>>.GetEnumerator()
		{
			for (int z = 0; z < RegionHelper.chunkDepth; z++)
				for (int x = 0; x < RegionHelper.chunkWidth; x++)
					if (updated[x, z].HasValue)
						yield return new KeyValuePair<Tuple<int, int>, DateTime>(new Tuple<int, int>(x, z), updated[x, z].Value);
		}

		#endregion

		#region IEnumerable<KeyValuePair<Tuple<int,int>,Tuple<DateTime,DataFile>>> Members

		IEnumerator<KeyValuePair<Tuple<int, int>, Tuple<DateTime, NamedBinaryTag.TagFile>>> IEnumerable<KeyValuePair<Tuple<int, int>, Tuple<DateTime, NamedBinaryTag.TagFile>>>.GetEnumerator()
		{
			for (int z = 0; z < RegionHelper.chunkDepth; z++)
				for (int x = 0; x < RegionHelper.chunkWidth; x++)
					if ((chunks[x, z].RootTag != null) && (updated[x, z].HasValue))
						yield return new KeyValuePair<Tuple<int, int>, Tuple<DateTime, NamedBinaryTag.TagFile>>(new Tuple<int, int>(x, z), new Tuple<DateTime, NamedBinaryTag.TagFile>(updated[x, z].Value, chunks[x, z]));
		}

		#endregion

		public void Save(System.IO.Stream output)
		{
			throw new NotImplementedException();
		}
	}
}
